package com.itedge.infrastructure.web.taskhandler.impl;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.context.MessageSource;
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.web.bind.ServletRequestDataBinder;

import com.itedge.infrastructure.constants.InfrastructureConstants;
import com.itedge.infrastructure.domain.IProcessEntity;
import com.itedge.infrastructure.service.process.IProcessService;
import com.itedge.infrastructure.service.task.ITaskService;
import com.itedge.infrastructure.service.taskdata.ITaskDataEntityService;
import com.itedge.infrastructure.taskdata.ITaskDataEntity;
import com.itedge.infrastructure.util.ContextUtil;
import com.itedge.infrastructure.web.dto.TaskAvailabilityResponse;
import com.itedge.infrastructure.web.taskhandler.ITaskDataHandler;
import com.itedge.infrastructure.web.taskhandler.TaskHandlerException;

/**
 * Abstract class containing some common functionality for task handlers.
 * 
 * @author jhe
 *
 * @param <T extends ITaskData>
 * @param <O extends IProcessEntity>
 */
public abstract class AbstractTaskDataHandler<T extends ITaskDataEntity, O extends IProcessEntity> 
	implements ITaskDataHandler<T, O> {

	/** Conversion service bean. */
    protected FormattingConversionServiceFactoryBean conversionService;
    
    /** Message source instance. */
	protected MessageSource messageSource;
	
	/** Process service instance. */
	protected IProcessService<O> processService;
	
	/** Task data service instance. */
	protected ITaskDataEntityService<T> taskDataService;
	
	/** Task service instance. */
	protected ITaskService<T,O> taskService;
	
	/**
	 * Default constructor, sets all needed bean and service instances.
	 * 
	 * @param conversionService
	 * @param processService
	 * @param taskDataService
	 * @param taskService
	 * @param messageSource
	 */
	protected AbstractTaskDataHandler(FormattingConversionServiceFactoryBean conversionService, 
			IProcessService<O> processService, ITaskDataEntityService<T> taskDataService,
			ITaskService<T,O> taskService, MessageSource messageSource) {
		this.conversionService = conversionService;
		this.messageSource = messageSource;
		this.processService = processService;
		this.taskDataService = taskDataService;
		this.taskService = taskService;
	}
	
	/**
	 * Implementing subclasses should return Class instance of related task data.
	 * 
	 * @return class instance
	 */
	abstract protected Class<T> getTaskDataClass();  
	
	@Override
	public Map<String,Object> loadTaskData(String taskId, O linkedObject, Boolean forHistory)
			throws TaskHandlerException {
		T taskData = taskDataService.findEntityByProcessTaskId(taskId);
    	if (taskData == null) {
    	    try {
				taskData = getTaskDataClass().newInstance();
			} catch (Exception e) {
				throw new TaskHandlerException(e.getMessage(),e.getCause());
			} 
    	} 
    	taskData.setTaskId(taskId);
    	Map<String,Object> data = new HashMap<String,Object>();
    	data.put("taskData", taskData);
    	return data;		
	}

	@Override
	public void saveTaskData(T taskData) {
		taskDataService.merge(taskData);		
	}
	
	@Override
	public void saveTaskDataAndPushProcess(T taskData, O linkedObject, String taskId) {
		taskService.completeTask(taskId, linkedObject, taskData, ContextUtil.getLoggedUsername());
	}

	@Override
	public TaskAvailabilityResponse requestTaskAvailabilityAndLockTask(String taskId, String lockedBy, Locale locale) {
		TaskAvailabilityResponse response = new TaskAvailabilityResponse();
		// check if task is available
		if (processService.getTaskById(taskId) != null) {
			// lock associated
			response.setAvailable(taskDataService.lock(taskId, lockedBy));			
		} else {
			response.setAvailable(false);
			response.setMessage(messageSource.getMessage(InfrastructureConstants.TASK_LOCKED_OR_COMPLETED,
					null, InfrastructureConstants.UNDEFINED_MESSAGE, locale));
		}
		return response;
	}
	
	@Override
	public TaskAvailabilityResponse requestTaskAvailability(String taskId, Locale locale) {
		TaskAvailabilityResponse response = new TaskAvailabilityResponse();
		// check if task is available
		if (processService.getTaskById(taskId) != null) {
			response.setAvailable(true);
		} else {
			response.setAvailable(false);
			response.setMessage(messageSource.getMessage(InfrastructureConstants.TASK_LOCKED_OR_COMPLETED,
					null, InfrastructureConstants.UNDEFINED_MESSAGE, locale));
		}
		return response;
	}

	@Override
	public T bindTaskData(HttpServletRequest request, O linkedObject)
			throws TaskHandlerException {
		T taskData;
		try {
			taskData = getTaskDataClass().newInstance();
		} catch (Exception e) {
			throw new TaskHandlerException(e.getMessage(),e.getCause());
		}
		ServletRequestDataBinder binder = new ServletRequestDataBinder(taskData);
		binder.setConversionService(conversionService.getObject());
		binder.bind(request);
		try {
			binder.closeNoCatch();
		} catch(Exception e) {
			throw new TaskHandlerException(e.getMessage(),e.getCause());
		}
		return taskData;
	}

}
